宏在C语言里及其重要,而在C++里用得就少多了。关于宏的第一规则是:绝不应该去使用它,除非你不得不这样做。几乎每个宏都表明了程序设计语言里,或者程序里,或者程序员的一个缺陷,因为它将在编译器看到程序的正文之前去重新摆布这些正文。宏也是许多程序设计工具的主要麻烦。所以,如果你使用了宏,你就应该准备着只能从各种工具(如排错系统、交叉引用工具、轮廓程序等)得到较少的服务。如果你必须使用宏,那么请仔细阅读你所用的C++预处理器实现的手册,而且不用过于自作聪明。还要警告读者,应该按照习惯,在为宏命名时使用许多大写字母。宏的语法在A.11节给出。

  简单的宏定义就像下面这样:

#define NAME rest of line

当NAME作为一个单词被遇到时,它就会被用字符序列rest of line(该行剩下的部分)取代。

例如,

named = NAME

将被展开为

named = rest of line

页可以定义带参数的宏,例如,

#define MAC(x, y) argument1:x argument2:y

在使用MAC时必须提供两个字符串,它们将在MAC()被展开时用于取代x和y,例如,

expanded = MAC(foo bar, yuk yuk)

将展开成

expanded = argument1:foo bar argument2:yuk yuk

宏名字不能重载,而且宏预处理器不能处理递归调用:

#define PRINT(a, b) cout << (a) << (b)
#define PRINT(a, b, c) cout << (a) << (b) << (c)    /* 麻烦? : 重复定义,不允许重载 */
#define FAC(n) (n > 1) ? n * FAC(n - 1) : 1         /* 麻烦:递归的宏 */

宏对字符串进行操作,它对C++的语法知之甚少,根本不知道C++的类型和作用域规则。编译器能看到的只是宏展开后的形式,所以在宏中的错误是在宏被展开之后的报告的,而不是在它定义时,这可能导致非常难以理解的错误信息。

  下面是一些可能有用的宏

#define CASE break;case
#define FOREVER for(;;)

下面是一些完全没有必要的宏:

#define PI 3.141593
#define BEGIN {
#define END }

下面是一些很危险的宏:

#define SQUARE(a) a*a
#define INCR_xx(xx)++

想知道它们为什么危险,请试着展开这个程序片段:

    int xx = 0;                  // 全局计数器

    void f()
    {
        int xx = 0;              // 局部变量
        int y = SQUARE(xx+2);    // y = xx + 2 * xx + 2; 即 y = xx + (2*xx) + 2
        INCR_xx;                 // 增加局部变量xx
    }

如果你要使用宏,在引用全局名字时一定要使用作用域解析运算符::(4.9.4节),并在所有可能的地方将出现的宏参数都用括号括起来。例如,

#define MIN(a, b) (((a)<(b)) ? (a) : (b))

如果你写的宏足够复杂,需要写注释时,采用/* */形式的注释是比较明智的,因为有时会有不懂C++的 // 注释的C预处理程序被用做C++工具的一部分。例如,

#define M2(a) something(a)  /* 细心的注释 */

通过利用宏,你可以设计出自己的私有语言。即使与C++相比你更偏爱自己的这种“增强的语言”,但对于大部分C++程序员而言,它也是不可理解的。进一步说,C预处理器是一种很简单的宏处理器。当你试图去做某些不那么简单的事情时,你多半会发现不能做好,或者做起来有不必要的困难。const、inline、template、enum和namespace机制等都是为了用做预处理器机构的许多传统使用方式的替代品。例如,

const int answer = 42;
template <class T> inline T min(T a, T b) { return (a < b) ? a : b; }

在写宏的时候,需要为某些东西提供新名字也是很常见的事情。通过##宏运算符可以拼接起两个串,构造出一个新串。例如,

#define NAME2(a,b) a##b
int NAME2(hack,cah)();

将产生

int hackcah();

提供给编译器去读。

  指令

#undef X

保证不再有称为X的有定义的宏---无论在此指令之前有还是没有。这种东西可以用于防止某些不想要的宏。然而,要想知道X对一片代码的作用是否如自己所设想的那样,那可不总是一件容易的事情。

🔚